package util;

import java.util.Date;
import java.util.NoSuchElementException;

/**
 * Utility class to validate the given arguments based on various checks. For
 * failed checks, methods throw a runtime exception. For passed checks methods
 * return back the arguments converted to a suitable type or void.
 * 
 * @author huangjm.2012, jaslin.wee.2012
 * 
 */
public class ValidationUtil {

	/**
	 * Checks if two ranges of date clashes with each other.
	 * 
	 * @param startDate
	 *            start of date range one
	 * @param endDate
	 *            end of date range one
	 * @param anotherStartDate
	 *            start of date range two
	 * @param anotherEndDate
	 *            end of date range two
	 * @throws IllegalArgumentException
	 *             if there is a clash
	 */
	public static void checkForDateClash(Date startDate, Date endDate,
			Date anotherStartDate, Date anotherEndDate) {
		boolean doesAnotherDateFallBetweenDate = (anotherStartDate
				.after(startDate) && anotherStartDate.before(endDate))
				|| (anotherEndDate.after(startDate) && anotherEndDate
						.before(endDate));
		boolean doesDateFallBetweenAnotherDate = (startDate
				.after(anotherStartDate) && startDate.before(anotherEndDate))
				|| (endDate.after(anotherStartDate) && endDate
						.before(anotherEndDate));
		if (startDate.equals(anotherStartDate)
				|| doesAnotherDateFallBetweenDate
				|| doesDateFallBetweenAnotherDate) {
			throw new IllegalArgumentException(startDate + "-" + endDate
					+ " clashes with " + anotherStartDate + "-"
					+ anotherEndDate);
		}
	}

	/**
	 * Checks if the given input string matches the given regex pattern.
	 * 
	 * @param input
	 * @param regex
	 * @return back the input string if the match passes
	 * @throws IllegalArgumentException
	 *             if the match fails
	 */
	public static String checkForRegexPatternMatch(String input, String regex) {
		if (input != null && !input.isEmpty() && input.matches(regex)) {
			return input;
		} else {
			throw new IllegalArgumentException(input
					+ " does not match regex pattern " + regex);
		}
	}

	/**
	 * Checks if the given date string matches the given pattern.
	 * 
	 * @param date
	 * @param format
	 * @return a converted date object for the given date string
	 * @throws IllegalArgumentException
	 *             if there is a mismatch
	 */
	public static Date checkForDateFormat(String date, String format,
			String regex) {
		checkForRegexPatternMatch(date, regex);
		return DateUtil.convertToDate(date, format);
	}

	/**
	 * Checks if two dates are one after another (i.e. end date is after start
	 * date).
	 * 
	 * @param startDate
	 * @param endDate
	 * @throws IllegalArgumentException
	 *             if end date is not after start date
	 */
	public static void checkForDateSequence(Date startDate, Date endDate) {
		if (!endDate.after(startDate)) {
			throw new IllegalArgumentException(endDate.toString()
					+ " is not after " + startDate.toString());
		}
	}

	/**
	 * Checks if the given number string is a double.
	 * 
	 * @param numberStr
	 * @return the given number in the double type
	 * @throws IllegalArgumentException
	 *             if the given number string is not a double
	 */
	public static double checkForDouble(String numberStr) {
		try {
			return Double.parseDouble(numberStr);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException(numberStr + " is not a double");
		}
	}

	/**
	 * Checks if the given object/element is null.
	 * 
	 * @param obj
	 * @throws NoSuchElementException
	 *             if the given object/element is null
	 */
	public static void checkForElement(Object obj) {
		if (obj == null) {
			throw new NoSuchElementException();
		}
	}

	/**
	 * Checks if the given number string is an integer.
	 * 
	 * @param numberStr
	 * @return the given number in the int type
	 * @throws IllegalArgumentException
	 *             if the given number is not an integer
	 */
	public static int checkForInteger(String numberStr) {
		try {
			return Integer.parseInt(numberStr);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException(numberStr + " is not an integer");
		}
	}

	/**
	 * Checks if the given decimal only contains a certain maximum number of
	 * decimal places.
	 * 
	 * @param decimal
	 *            the decimal string to check for maximum number of decimal
	 *            places
	 * @param maxNumDecimalPlaces
	 *            the maximum number of decimal places that the given decimal
	 *            string can contain
	 * @return the decimal converted to the double type
	 * @throws IllegalArgumentException
	 *             if the given decimal string is not a decimal at all or
	 *             contains more than the given maximum number of decimal places
	 */
	public static double checkForMaxDecimalPlaces(String decimal,
			int maxNumDecimalPlaces) {
		// use try-catch because index out of bound exception might be thrown
		try {
			int decimalPointIndex = decimal.indexOf('.');
			if (decimalPointIndex > -1) {
				String decimalPortion = decimal.substring(
						decimalPointIndex + 1, decimal.length());
				if (decimalPortion.length() > maxNumDecimalPlaces) {
					throw new Exception();
				}
			}
		} catch (Exception e) {
			throw new IllegalArgumentException(decimal
					+ " is not a decimal with only a maximum number of "
					+ maxNumDecimalPlaces + " d.p.");
		}

		return checkForDouble(decimal);

	}

	/**
	 * Checks if the given number at least equal to the given minimum value.
	 * 
	 * @param number
	 * @param minValue
	 * @return the given number converted to double type
	 * @throws IllegalArgumentException
	 *             if the given number is less than the minimum value
	 */
	public static double checkForMinValue(double number, double minValue) {
		if (number < minValue) {
			throw new IllegalArgumentException(number + " is less than "
					+ minValue);
		}

		return number;
	}

	/**
	 * Checks if the given number is not less than zero.
	 * 
	 * @param number
	 * @return back the given number
	 * @throws IllegalArgumentException
	 *             if the given number is less than zero
	 */
	public static double checkForNonNegetive(double number) {
		if (number < 0) {
			throw new IllegalArgumentException(number + " is negative");
		}
		return number;
	}

	/**
	 * Checks if the given object is null.
	 * 
	 * @param obj
	 * @throws NullPointerException
	 *             if the given object is null
	 */
	public static void checkForNull(Object obj) {
		if (obj == null) {
			throw new NullPointerException();
		}
	}

	/**
	 * Checks if the given number is more than zero.
	 * 
	 * @param number
	 * @return back the given number
	 * @throws IllegalArgumentException
	 *             if the given number is zero or less than zero
	 */
	public static double checkForPositive(double number) {
		if (number < 1) {
			throw new IllegalArgumentException(number + " is not positive");
		}
		return number;
	}

	/**
	 * Checks if the given number is more than zero.
	 * 
	 * @param number
	 * @return back the given number
	 * @throws IllegalArgumentException
	 *             if the given number is zero or less than zero
	 */
	public static int checkForPositive(int number) {
		if (number < 1) {
			throw new IllegalArgumentException(number + " is not positive");
		}
		return number;
	}

	/**
	 * Checks if the given number falls between the given lowest and highest
	 * value, inclusive.
	 * 
	 * @param number
	 * @param lowestPossibleValue
	 * @param highestPossibleValue
	 * @return back the given number
	 * @throws IllegalArgumentException
	 *             if the given number falls below the lowest value or above the
	 *             highest value
	 */
	public static double checkForRange(double number,
			double lowestPossibleValue, double highestPossibleValue) {
		if (number >= lowestPossibleValue && number <= highestPossibleValue) {
			return number;
		}
		throw new IllegalArgumentException(number + " not in range "
				+ lowestPossibleValue + ".." + highestPossibleValue);
	}

	/**
	 * Checks if the given number falls between the given lowest and highest
	 * value, inclusive.
	 * 
	 * @param number
	 * @param lowestPossibleValue
	 * @param highestPossibleValue
	 * @return back the given number
	 * @throws IllegalArgumentException
	 *             if the given number falls below the lowest value or above the
	 *             highest value
	 */
	public static int checkForRange(int number, int lowestPossibleValue,
			int highestPossibleValue) {
		if (number >= lowestPossibleValue && number <= highestPossibleValue) {
			return number;
		}
		throw new IllegalArgumentException(number + " not in range "
				+ lowestPossibleValue + ".." + highestPossibleValue);
	}

}
